package com.site.controller;

import com.site.util.AESUtil;
import com.site.util.JacksonUtil;
import com.site.util.RSAUtil;
import lombok.Data;
import org.springframework.util.StringUtils;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * RSA+AES实现接口验签和参数加密
 */
public class EntryRSATest {

    private static String  getUUID32(){
        return UUID.randomUUID().toString().replace("-","");
    }


    public static void main(String[] args) {
        /****先给调用方分配一组RSA密钥和一个appId****/
        //初始化RSA密钥
        Map<String, Object> init = RSAUtil.init();
        //私钥
        String privateKey = RSAUtil.getPrivateKey(init);
        //公钥
        String publicKey = RSAUtil.getPublicKey(init);
        //appId，32位的uuid
        String appId = getUUID32();
        /****先给调用方分配一组RSA密钥和一个appId****/

        //使用appId的前16位作为AES密钥，并对密钥进行rsa公钥加密
        String aseKey = appId.substring(0, 16);

        //----------------------------------调用方（请求方）------------------------------------
        JsonRequest jsonRequest = consumer(appId,aseKey, publicKey, privateKey);

        //----------------------------------接收方（自己的系统）------------------------------------
        String requestBody = provider(publicKey, privateKey, jsonRequest);
        System.out.println(requestBody);
    }

    /**
     * 接收方（自己的系统）
     * @param publicKey
     * @param privateKey
     * @param jsonRequest
     */
    private static String provider(String publicKey,String privateKey,JsonRequest jsonRequest){
        /*****接收方（自己的系统）*****/
        //参数判空（略）
        //appId校验（略）
        //本条请求的合法性校验《唯一不重复请求；时间合理》（略）
        // 1.验签
        Map<String, Object> paramMap2 = RSAUtil.bean2Map(jsonRequest);
        paramMap2.remove("sign");
        //参数排序
        Map<String, Object> sortedMap2 = RSAUtil.sort(paramMap2);
        //拼接参数：key1Value1key2Value2
        String urlParams2 = RSAUtil.groupStringParam(sortedMap2);
        //签名验证
        boolean verify = RSAUtil.verify(urlParams2.getBytes(), publicKey, jsonRequest.getSign());
        if (!verify) {
            throw new RuntimeException("签名验证失败");
        }
        String aseKey = RSAUtil.decryptByPrivateKey(jsonRequest.getAseKey().getBytes(), privateKey);
        // 2.私钥解密，获取aseKey
        if (!StringUtils.isEmpty(jsonRequest.getBody())) {
            // 解密请求报文
            String requestBody = "";
            try {
                requestBody = AESUtil.decrypt(jsonRequest.getBody(), aseKey, jsonRequest.getAppId().substring(16));
            } catch (Exception e) {
                throw new RuntimeException("请求参数解密异常");
            }
            System.out.println("业务参数解密结果："+requestBody);
            return requestBody;
        }
        /*****接收方（自己的系统）*****/
        return null;
    }

    /**
     * 调用方（请求方）
     * @param appId
     * @param publicKey
     * @param privateKey
     * @return
     */
    private static JsonRequest consumer(String appId,String aseKey,String publicKey,String privateKey){
        //----------------------------------调用方（请求方）------------------------------------
        /*****调用方（请求方）*****/
        // 1.业务参数
        Map<String,Object>  businessParams = new HashMap<>();
        businessParams.put("name","小二");
        businessParams.put("job","程序猿");
        businessParams.put("hobby","打篮球");

        JsonRequest jsonRequest = new JsonRequest();
        jsonRequest.setRequestId(getUUID32());
        jsonRequest.setAppId(appId);
        jsonRequest.setTimestamp(System.currentTimeMillis());
        byte[] enStr = RSAUtil.encryptByPublicKey(aseKey, publicKey);// 对密钥进行rsa公钥加密
        jsonRequest.setAseKey(new String(enStr));
        // 2.请求的业务参数进行 aes 加密
        String body = "";
        try {
            body = AESUtil.encrypt(JacksonUtil.beanToJsonString(businessParams), aseKey, appId.substring(16));
        } catch (Exception e) {
            throw new RuntimeException("报文加密异常", e);
        }
        jsonRequest.setBody(body);
        //签名
        Map<String, Object> paramMap = RSAUtil.bean2Map(jsonRequest);// jsonRequest 转 map
        paramMap.remove("sign");
        // 参数排序
        Map<String, Object> sortedMap = RSAUtil.sort(paramMap);
        // 拼接参数：key1Value1key2Value2
        String urlParams = RSAUtil.groupStringParam(sortedMap);
        //私钥签名
        String sign = RSAUtil.sign(urlParams.getBytes(), privateKey);
        jsonRequest.setSign(sign);
        System.out.println("业务参数加密结果："+jsonRequest);
        /*****调用方（请求方）*****/
        return jsonRequest;
    }

    /**
     * serviceId：服务id（接口id）。接口设计分为两种，
     *      一种是所有的调用方针对类似的业务，都调用的是同一接口地址，然后内部系统根据serviceId去判断具体是要调用哪个业务方法；
     *      另一种是针对不同的调用方，开发不同的接口，接口地址也是不一样，那么这个时候就可以不要serviceId这个字段。
     *      本章是使用第二种方式，所以serviceId可以不要（可空）。
     * requestId：请求唯一id。方便查询定位某个请求和防止同个请求多次调用。
     * appId：商户id，即我们会给调用方分配一个这样的id，并且将这个id与调用方的信息进行关联，比如“通过appId查询出调用方的加密密钥等”
     * aseKey：是AES对称加密的密钥。用于解密业务请求参数。这里要先用RSA公钥对aseKey进行加密后传输。
     * timestamp：请求时间戳。可以用该字段，对请求的时间合法性进行校验。（如：过滤掉请求时间不在当前时间的正负10分钟范围内的请求）
     * body：请求的业务参数。对请求的业务参数AES加密后再赋值
     */
    @Data
    public static class JsonRequest{
        //接口id 可空
        private String serviceId;
        //请求唯一id 非空
        private String requestId;
        //商户id 非空
        private String appId;
        //参数签名 非空
        private String sign;
        //对称加密key 非空
        private String aseKey;
        //时间戳，精确到毫秒 非空
        private long timestamp;
        //请求的业务参数(AES加密后传入) 可空
        private String body;

    }
}